//
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_

#include <cstddef>

#include <memory>
#include <string>
#include <vector>

#include <brillo/key_value_store.h>
#include <brillo/secure_blob.h>

#include "bsdiff/constants.h"
#include "update_engine/payload_generator/filesystem_interface.h"
#include "update_engine/update_metadata.pb.h"

namespace chromeos_update_engine {

struct PostInstallConfig {
  // Whether the postinstall config is empty.
  bool IsEmpty() const;

  // Whether this partition carries a filesystem with post-install program that
  // must be run to finalize the update process.
  bool run = false;

  // The path to the post-install program relative to the root of this
  // filesystem.
  std::string path;

  // The filesystem type used to mount the partition in order to run the
  // post-install program.
  std::string filesystem_type;

  // Whether this postinstall script should be ignored if it fails.
  bool optional = false;
};

// Data will be written to the payload and used for hash tree and FEC generation
// at device update time.
struct VerityConfig {
  // Whether the verity config is empty.
  bool IsEmpty() const;

  // Clears this config, subsequent calls to "IsEmpty" will return true.
  void Clear();

  // The extent for data covered by verity hash tree.
  Extent hash_tree_data_extent;

  // The extent to store verity hash tree.
  Extent hash_tree_extent;

  // The hash algorithm used in verity hash tree.
  std::string hash_tree_algorithm;

  // The salt used for verity hash tree.
  brillo::Blob hash_tree_salt;

  // The extent for data covered by FEC.
  Extent fec_data_extent;

  // The extent to store FEC.
  Extent fec_extent;

  // The number of FEC roots.
  uint32_t fec_roots = 0;
};

struct PartitionConfig {
  explicit PartitionConfig(std::string name) : name(name) {}
  static CompressionAlgorithm ParseCompressionParam(std::string_view param);
  static CompressionAlgorithm GetDefaultCompressionParam() {
    CompressionAlgorithm algo;
    algo.set_type(CompressionAlgorithm::LZ4HC);
    algo.set_level(9);
    return algo;
  }

  // Returns whether the PartitionConfig is not an empty image and all the
  // fields are set correctly to a valid image file.
  bool ValidateExists() const;

  // Open then filesystem stored in this partition and stores it in
  // |fs_interface|. Returns whether opening the filesystem worked.
  bool OpenFilesystem();

  // The path to the partition file. This can be a regular file or a block
  // device such as a loop device.
  std::string path;

  // The path to the .map file associated with |path| if any. The .map file is
  // generated by the Android filesystem generation tools when creating a
  // filesystem and describes the blocks used by each file.
  std::string mapfile_path;

  // The size of the data in |path|. If rootfs verification is used (verity)
  // this value should match the size of the verity device for the rootfs, and
  // the size of the whole kernel. This value could be smaller than the
  // partition and is the size of the data update_engine assumes verified for
  // the source image, and the size of that data it should generate for the
  // target image.
  uint64_t size = 0;

  // The FilesystemInterface implementation used to access this partition's
  // files.
  std::unique_ptr<FilesystemInterface> fs_interface;

  std::string name;

  PostInstallConfig postinstall;
  VerityConfig verity;

  // Enables the on device fec data computation by default.
  bool disable_fec_computation = false;

  // Per-partition version, usually a number representing timestamp.
  std::string version;

  // parameter passed to mkfs.erofs's -z option.
  // In the format of "compressor,compression_level"
  // Examples: lz4    lz4hc,9
  // The default is usually lz4hc,9 for mkfs.erofs
  CompressionAlgorithm erofs_compression_param = GetDefaultCompressionParam();
};

// The ImageConfig struct describes a pair of binaries kernel and rootfs and the
// metadata associated with the image they are part of, like build number, size,
// etc.
struct ImageConfig {
  // Returns whether the ImageConfig is an empty image.
  bool ValidateIsEmpty() const;

  // Load |rootfs_size| and |kernel.size| from the respective image files. For
  // the kernel, the whole |kernel.path| file is assumed. For the rootfs, the
  // size is detected from the filesystem.
  // Returns whether the image size was properly detected.
  bool LoadImageSize();

  // Load postinstall config from a key value store.
  bool LoadPostInstallConfig(const brillo::KeyValueStore& store);

  // Load verity config by parsing the partition images.
  bool LoadVerityConfig();

  // Load dynamic partition info from a key value store.
  bool LoadDynamicPartitionMetadata(const brillo::KeyValueStore& store);

  // Validate |dynamic_partition_metadata| against |partitions|.
  bool ValidateDynamicPartitionMetadata() const;

  // The updated partitions.
  std::vector<PartitionConfig> partitions;

  // The super partition metadata.
  std::unique_ptr<DynamicPartitionMetadata> dynamic_partition_metadata;
};

struct PayloadVersion {
  PayloadVersion() : PayloadVersion(0, 0) {}
  PayloadVersion(uint64_t major_version, uint32_t minor_version);

  // Returns whether the PayloadVersion is valid.
  bool Validate() const;

  // Return whether the passed |operation| is allowed by this payload.
  bool OperationAllowed(InstallOperation::Type operation) const;

  // Whether this payload version is a delta or partial payload.
  bool IsDeltaOrPartial() const;

  // The major version of the payload.
  uint64_t major;

  // The minor version of the payload.
  uint32_t minor;
};

// The PayloadGenerationConfig struct encapsulates all the configuration to
// build the requested payload. This includes information about the old and new
// image as well as the restrictions applied to the payload (like minor-version
// and full/delta payload).
struct PayloadGenerationConfig {
  // Returns whether the PayloadGenerationConfig is valid.
  bool Validate() const;

  void ParseCompressorTypes(const std::string& compressor_types);

  // Image information about the new image that's the target of this payload.
  ImageConfig target;

  // Image information pertaining the old image, if any. This is only valid
  // if is_full is false, so we are requested a delta payload.
  ImageConfig source;

  // Whether the requested payload is a delta payload.
  bool is_delta = false;

  // Whether the requested payload is a partial payload, i.e. only update a
  // subset of partitions on device.
  bool is_partial_update = false;

  // The major/minor version of the payload.
  PayloadVersion version;

  // The size of the rootfs partition, that not necessarily is the same as the
  // filesystem in either source or target version, since there is some space
  // after the partition used to store the verity hashes and or the bootcache.
  uint64_t rootfs_partition_size = 0;

  // The |hard_chunk_size| is the maximum size that a single operation should
  // write in the destination. Operations bigger than chunk_size should be
  // split. A value of -1 means no hard chunk size limit. A very low limit
  // means more operations, and less of a chance to reuse the data.
  ssize_t hard_chunk_size = -1;

  // The |soft_chunk_size| is the preferred chunk size to use when there's no
  // significant impact to the operations. For example, REPLACE, MOVE and
  // SOURCE_COPY operations are not significantly impacted by the chunk size,
  // except for a few bytes overhead in the manifest to describe extra
  // operations. On the other hand, splitting BSDIFF operations impacts the
  // payload size since it is not possible to use the redundancy *between*
  // chunks.
  size_t soft_chunk_size = 2 * 1024 * 1024;

  // TODO(deymo): Remove the block_size member and maybe replace it with a
  // minimum alignment size for blocks (if needed). Algorithms should be able to
  // pick the block_size they want, but for now only 4 KiB is supported.

  // The block size used for all the operations in the manifest.
  size_t block_size = 4096;

  // The maximum timestamp of the OS allowed to apply this payload.
  int64_t max_timestamp = 0;

  // Path to apex_info.pb, extracted from target_file.zip
  std::string apex_info_file;

  // Whether to enable VABC xor op
  bool enable_vabc_xor = false;

  // Whether to enable LZ4diff ops
  bool enable_lz4diff = false;

  // Whether to enable zucchini ops
  bool enable_zucchini = true;

  std::string security_patch_level;

  uint32_t max_threads = 0;

  std::vector<bsdiff::CompressorType> compressors{
      bsdiff::CompressorType::kBZ2, bsdiff::CompressorType::kBrotli};

  [[nodiscard]] bool OperationEnabled(InstallOperation::Type op) const noexcept;
};

}  // namespace chromeos_update_engine

#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
